//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
//////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2003 Intrinsyc Europe Ltd.
//////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2004 Siemens AG
//
// Version: 1.1.2.5
// Date:    11. Jan. 2006
//////////////////////////////////////////////////////////////////////////////
//
//
// (C) Copyright 2007 Marvell International Ltd.
//  All Rights Reserved
//


#include "precomp.h"

#define IOCTL_SPKR_ENABLE   0
#define IOCTL_SPKR_DISABLE  0

#define AUDIO_VOLUME_NOT_SET 0xFFFFFFFF

// This driver doesn't have a way to query current
// uplink and downlink volume, so we have to cache them.
DWORD   g_dwCacheUplinkVolume = 0;
DWORD   g_dwCacheDownlinkVolume = 0;
// Temporary cahce values.
DWORD   g_dwCacheUplinkVolumeTemp = 0;
DWORD   g_dwCacheDownlinkVolumeTemp = 0;

extern RHA_FUNCTION_LIST RHAFunctionList;
extern DWORD g_dwEnableSNFASetting;
extern INT    g_iNrCallInfo;

BOOL g_fNoMuteCmdSoFar = TRUE;
UINT g_dwLastMuteSetting = 0;

//
//
//
static HRESULT ParseGetAudioGain(LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam)
{
    TBD_FUNCTION(ParseGetAudioGain);
    RILGAININFO* prgi = NULL;
    HRESULT hr = S_OK;

    pBlob = NULL;
    cbBlob = 0;

    prgi = (RILGAININFO*)AllocBlob(sizeof(RILGAININFO));
    if (!prgi) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    memset(prgi, 0x00, sizeof(RILGAININFO));
    prgi->cbSize = sizeof(RILGAININFO);

    prgi->dwRxGain = g_dwCacheDownlinkVolume;
    prgi->dwParams |= RIL_PARAM_GI_RXGAIN;

    prgi->dwTxGain = g_dwCacheUplinkVolume;
    prgi->dwParams |= RIL_PARAM_GI_TXGAIN;

    pBlob = (void*)prgi;
    cbBlob = sizeof(RILGAININFO);

Error:
    if (FAILED(hr)) {
        FreeBlob(prgi);
    }
    return hr;
}


//
//
//
HRESULT RILDrv_GetAudioGain(DWORD dwParam)
{
    TBD_FUNCTION(RILDrv_GetAudioGain);
    HRESULT hr = S_OK;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    // When RILDrv_SetAudioGain() is called the command port might be blocked by an ATD,
    // so we have to try to reroute the command to another multiplexer channel, which
    // is availabe.
    if (!QueueCmd(URC_PORT, pHandle, NULL, CMDOPT_NOOP, APIID_GETAUDIOGAIN, ParseGetAudioGain, NULL, hr)) {
        hr = E_FAIL;
        goto Error;
    }

Error:
    return hr;
}

static HRESULT ParseSetAudioGain(LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam)
{
    TBD_FUNCTION(ParseSetAudioGain);
    HRESULT hr = S_OK;

    pBlob = NULL;
    cbBlob = 0;

    // We store the cached values
    g_dwCacheDownlinkVolume = g_dwCacheDownlinkVolumeTemp;
    g_dwCacheUplinkVolume = g_dwCacheUplinkVolumeTemp;

    return hr;
}

//
//
//
HRESULT RILDrv_SetAudioGain(DWORD dwParam, const RILGAININFO* lpGainInfo)
{
    COM_PORT_TYPE iPort;
    TBD_FUNCTION(RILDrv_SetAudioGain);
    char szCmd[MAX_ATCMD_LEN];
    LPSTR szWalk = szCmd;
    HRESULT hr = S_OK;
    DWORD dwMcVolume, dwAcVolume, dwDevice;
    DWORD dwGain, dwSideTone;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    if (!(lpGainInfo->dwParams & RIL_PARAM_GI_RXGAIN) && !(lpGainInfo->dwParams & RIL_PARAM_GI_TXGAIN)) {
        hr = E_INVALIDARG;
        goto Error;
    }

    // When RILDrv_SetAudioGain() is called the command port might be blocked by an ATD,
    // so we have to try to reroute the command to another multiplexer channel, which
    // is availabe.
#ifdef AUXCOM_PORT
    // The auxiliary channel is always available for commands with short execution time.
    iPort = URC_PORT;
#else
    // Since there is no auxiliary channel available in the DualUART version, we have to use
    // the data port if available or risk to temporary block the command until ATD is finished.
    iPort = (pHandle->GetDevice()->GetComDevice(DATA_PORT)->FDataMode() ? COMMAND_PORT : DATA_PORT);
#endif

#ifdef TBD_DEBUG
    TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("SetAudioGain(RX gain = %d TX gain = %d)"), lpGainInfo->dwRxGain>>24, lpGainInfo->dwTxGain>>24);
#endif

    g_dwCacheDownlinkVolumeTemp = g_dwCacheDownlinkVolume;
    g_dwCacheUplinkVolumeTemp = g_dwCacheUplinkVolume;

    (void)strncpyz(szWalk, "AT", MAX_ATCMD_LEN - (szWalk - szCmd));
    szWalk = strchr(szWalk, '\0');  // NO_TYPO: 27
    TBD_ASSERT(NULL != szWalk);

    if (lpGainInfo->dwParams & RIL_PARAM_GI_RXGAIN) {
        dwDevice = pHandle->GetDevice()->GetAudioRxDevice();
        g_dwCacheDownlinkVolumeTemp = lpGainInfo->dwRxGain;

        dwMcVolume = g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwRxMcSteps[(lpGainInfo->dwRxGain>>24)/51];
        dwAcVolume = g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwRxAcSteps[(lpGainInfo->dwRxGain>>24)/51];
        dwGain = g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwGain;
        dwSideTone = g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwSideToneSteps[(lpGainInfo->dwRxGain>>24)/51];

        RHAFunctionList.pfnRHA_AdjustRxVolume(dwDevice, dwAcVolume);

        if(dwMcVolume != AUDIO_VOLUME_NOT_SET) {
            // Special workaround for MC5x, MC4x: when changing the outCalibrate parameter while
            // module is in dialing or alerting state, AT^SNFV have to be called twice to force
            // the module to use the new values.

            if (g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwAudioMode > 1) {
                if (IS_ADI)
                    (void)_snprintfz(szWalk, MAX_ATCMD_LEN - (szWalk - szCmd), "^SNFO=%u,%u,%u,%u,%u,%u,4,%u", dwGain, dwMcVolume, dwMcVolume, dwMcVolume, dwMcVolume, dwMcVolume, dwSideTone);
                else
                    (void)_snprintfz(szWalk, MAX_ATCMD_LEN - (szWalk - szCmd), "^SNFO=%u,%u,%u,%u,%u,%u,4,%u;^SNFV=3;^SNFV=4", dwGain, dwMcVolume, dwMcVolume, dwMcVolume, dwMcVolume, dwMcVolume, dwSideTone);
            }
            szWalk = strchr(szWalk, '\0');  // NO_TYPO: 27
            TBD_ASSERT(NULL != szWalk);
        }
        if (!(lpGainInfo->dwParams & RIL_PARAM_GI_TXGAIN)) {
            dwAcVolume = g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwTxAcSteps[MAX_VOLUME_STEPS-1];
            if(dwAcVolume != AUDIO_VOLUME_NOT_SET)
                RHAFunctionList.pfnRHA_AdjustTxVolume(dwDevice, dwAcVolume);
        }
    }

    if (lpGainInfo->dwParams & RIL_PARAM_GI_TXGAIN) {
        dwDevice = pHandle->GetDevice()->GetAudioTxDevice();
        g_dwCacheUplinkVolumeTemp = lpGainInfo->dwTxGain;

        dwMcVolume = g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwTxMcSteps[(lpGainInfo->dwTxGain>>24)/51];
        dwAcVolume = g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwTxAcSteps[(lpGainInfo->dwTxGain>>24)/51];

        RHAFunctionList.pfnRHA_AdjustTxVolume(dwDevice, dwAcVolume);

        if(dwMcVolume != AUDIO_VOLUME_NOT_SET) {
            if (g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwAudioMode > 1 && g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwAudioMode > 1 && g_dwEnableSNFASetting == 1) {
                if (lpGainInfo->dwParams & RIL_PARAM_GI_RXGAIN) {
                    (void)strncpyz(szWalk, ";", MAX_ATCMD_LEN - (szWalk - szCmd));
                    szWalk = strchr(szWalk, '\0');  // NO_TYPO: 27
                    TBD_ASSERT(NULL != szWalk);
                }
                (void)_snprintfz(szWalk, MAX_ATCMD_LEN - (szWalk - szCmd), "^SNFA=%u", dwMcVolume);
                szWalk = strchr(szWalk, '\0');  // NO_TYPO: 27
                TBD_ASSERT(NULL != szWalk);
            }
        }
    }
    (void)strncpyz(szWalk, "\r", MAX_ATCMD_LEN - (szWalk - szCmd));  // NO_TYPO: 30

    if (!QueueCmd(iPort, pHandle, szCmd, CMDOPT_NONE, APIID_SETAUDIOGAIN, ParseSetAudioGain, NULL, hr)) {
        hr = E_FAIL;
        goto Error;
    }

Error:
    return hr;
}


//
//
//
HRESULT RILDrv_SetAudioDevices(DWORD dwParam, const RILAUDIODEVICEINFO* lpAudioDeviceInfo)
{
    TBD_FUNCTION(RILDrv_SetAudioDevices);
    RILAUDIODEVICEINFO lAudioDeviceInfo;
    char szCmd[MAX_ATCMD_LEN];
    HRESULT hr = S_OK;
    DWORD dwMcVolume = 0, dwAcVolume = 0, dwDevice = 0;
    
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    memcpy(&lAudioDeviceInfo, lpAudioDeviceInfo, sizeof(RILAUDIODEVICEINFO));

    if (RHAFunctionList.pfnRHA_ConfirmAudioMode != NULL)
    {
        DWORD dwTxDevice, dwRxDevice;

        dwTxDevice = lAudioDeviceInfo.dwTxDevice;
        dwRxDevice = lAudioDeviceInfo.dwRxDevice;
        if (!RHAFunctionList.pfnRHA_ConfirmAudioMode(&dwTxDevice, &dwRxDevice)) {
            RILRetailTrace((TEXT("RilDrv: Settings of RILDrv_SetAudioDevices() overwritten by RHA from %d/%d to %d/%d\r\n"), lAudioDeviceInfo.dwTxDevice, lAudioDeviceInfo.dwTxDevice, dwTxDevice, dwRxDevice));
            lAudioDeviceInfo.dwTxDevice = dwTxDevice;
            lAudioDeviceInfo.dwRxDevice = dwRxDevice;
        }
    }
#ifdef TBD_DEBUG
    TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("SetAudioDevices() - "));
#endif
    if (lAudioDeviceInfo.dwParams & RIL_PARAM_ADI_TXDEVICE) {
        DWORD dwAudioMode = 0;

        dwDevice = lAudioDeviceInfo.dwTxDevice;
        RILRetailTrace((TEXT("RilDrv: SetAudioDevices() - tx device = %d\r\n"), dwDevice));
        dwAudioMode = g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwAudioMode;

        if (dwAudioMode == 0)
            strcpy(szCmd, "AT\r");
        else
        {
            if (g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwInterface != 0 && dwAudioMode > 1)
            {
                sprintf(szCmd, "AT^SNFS=%d;^SAIC=%d,%d,%d\r", dwAudioMode, g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwInterface, g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwMicrophone, g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwEarpiece);
            }
            else
            {
                sprintf(szCmd, "AT^SNFS=%d\r", dwAudioMode);
            }
            if (g_dwModemType == MODEMTYPE_SIEMENS_MC75 && g_dwModemVersion == 2001 && g_iNrCallInfo > 0)
            {
                char* szWork = strchr(szCmd, '\r');

                // MC75 V2.001: after changing the audio mode while a call is in progress, we have to repeat the AT+CMUT as well
                sprintf(szWork, ";+CMUT=%d\r", g_fNoMuteCmdSoFar ? 0 : g_dwLastMuteSetting);
            }
        }

        pHandle->GetDevice()->SetAudioTxDevice(dwDevice);

        dwAcVolume = g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwRxAcSteps[(g_dwCacheDownlinkVolume >> 24) / 51];
        RHAFunctionList.pfnRHA_AdjustRxVolume(dwDevice, dwAcVolume);

        dwAcVolume = g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwTxAcSteps[(g_dwCacheUplinkVolume >> 24) / 51];
        RHAFunctionList.pfnRHA_AdjustTxVolume(dwDevice, dwAcVolume);

        if (strstr(szCmd, "^SNFS") && dwAudioMode > 1) {

            char* szWork = strchr(szCmd, '\r');

            if (szWork) {
                dwMcVolume = g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwRxMcSteps[(g_dwCacheDownlinkVolume >> 24) / 51];
                if (dwMcVolume != AUDIO_VOLUME_NOT_SET) {
                    (void)_snprintfz(szWork, MAX_ATCMD_LEN - (szWork - szCmd), ";^SNFO=%u,%u,%u,%u,%u,%u,4,%u\r", g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwGain, dwMcVolume, dwMcVolume, dwMcVolume, dwMcVolume, dwMcVolume, g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwSideToneSteps[(g_dwCacheDownlinkVolume >> 24) / 51]);
                }
            }
            if (g_dwEnableSNFASetting == 1) {
                char* szWork = strchr(szCmd, '\r');
                if (szWork) {
                    dwMcVolume = g_acAudioConfig[GetAudioTableIndex(dwDevice)].dwTxMcSteps[(g_dwCacheUplinkVolume >> 24) / 51];
                    if (dwMcVolume != AUDIO_VOLUME_NOT_SET) {
                        (void)_snprintfz(szWork, MAX_ATCMD_LEN - (szWork - szCmd), ";^SNFA=%u\r", dwMcVolume);
                    }
                }
            }
        }
    }

    if (lAudioDeviceInfo.dwParams & RIL_PARAM_ADI_RXDEVICE) {
        dwDevice = lAudioDeviceInfo.dwRxDevice;
#ifdef TBD_DEBUG
        RILRetailTrace((TEXT("RilDrv: SetAudioDevices() - rx device = %d\r\n"), dwDevice));
        switch(dwDevice)
        {
        case RIL_AUDIO_NONE:
            TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("RX device = RIL_AUDIO_NONE"));
            break;
        case RIL_AUDIO_HANDSET:
            TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("RX device = RIL_AUDIO_HANDSET"));
            break;
        case RIL_AUDIO_SPEAKERPHONE:
            TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("RX device = RIL_AUDIO_SPEAKERPHONE"));
            break;
        case RIL_AUDIO_HEADSET:
            TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("RX device = RIL_AUDIO_HEADSET"));
            break;
        case RIL_AUDIO_CARKIT:
            TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("RX device = RIL_AUDIO_CARKIT"));
            break;
        case RIL_AUDIO_SIEMENS_BLUETOOTHHEADSET:
            TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("RX device = RIL_AUDIO_SIEMENS_BLUETOOTHHEADSET"));
            break;
        case RIL_AUDIO_SIEMENS_BLUETOOTHCARKIT:
            TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("RX device = RIL_AUDIO_SIEMENS_BLUETOOTHCARKIT"));
            break;
        case RIL_AUDIO_SIEMENS_GENERIC1:
            TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("RX device = RIL_AUDIO_SIEMENS_GENERIC1"));
            break;
        case RIL_AUDIO_SIEMENS_GENERIC2:
            TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("RX device = RIL_AUDIO_SIEMENS_GENERIC2"));
            break;
        default:
            TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("Error - Unknown RX device"));
            break;
        }
#endif
        pHandle->GetDevice()->SetAudioRxDevice(dwDevice);
    }

    //Current, it seams that these AT cmd will cause modem to shutdown during incoming call
    /*
    if (lAudioDeviceInfo.dwParams & RIL_PARAM_ADI_TXDEVICE) {
        if (!QueueCmdWithRetry(PRIORITY_PORT, pHandle, szCmd, CMDOPT_NONE, APIID_SETAUDIODEVICES, NULL, NULL, hr, 1, 1000)) {
            hr = E_FAIL;
            goto Error;
        }
    }
    else {
        if (!QueueCmd(URC_PORT, pHandle, NULL, CMDOPT_NOOP, APIID_SETAUDIODEVICES, NULL, NULL, hr)) {
            hr = E_FAIL;
            goto Error;
        }
    }
    */

Error:

    return hr;
}

//
//
//
/*HRESULT RILDrv_GetAudioDevices(DWORD dwParam)
{
    TBD_FUNCTION(RILDrv_GetAudioDevices);
#ifdef TBD_DEBUG
    TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("GetAudioDevices()"));
#endif
    HRESULT hr = S_OK;

    return hr;
}
*/
//
//
//
static HRESULT ParseGetAudioMuting(LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam)
{
    TBD_FUNCTION(ParseGetAudioMuting);
    UINT nValue;
    BOOL* pfEnabled = NULL;
    HRESULT hr = S_OK;

    pBlob = NULL;
    cbBlob = 0;

    pfEnabled = (BOOL*)AllocBlob(sizeof(BOOL));
    if (!pfEnabled) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    memset(pfEnabled, 0x00, sizeof(BOOL));

    // Parse "<prefix>+CMUT: <enabled><postfix>"
    if (!ParseRspPrefix(szRsp, szRsp)                           ||
        !MatchStringBeginning(szRsp, "+CMUT: ", szRsp)          ||
        !ParseUIntAndVerifyAbove(szRsp, TRUE, 2, nValue, szRsp) ||
        !ParseRspPostfix(szRsp, szRsp)) {
        hr = E_FAIL;
        goto Error;
    }

    *pfEnabled = (1 == nValue);
    g_dwLastMuteSetting = nValue;

    pBlob = (void*)pfEnabled;
    cbBlob = sizeof(BOOL);

Error:
    if (FAILED(hr)) {
        FreeBlob(pfEnabled);
    }
    return hr;
}

// This Parsing function always returns not muted. This is a workaround since the MC75 and TC63 sometimes report
// that audio is muted even it is known to be not muted.
static HRESULT ParseGetAudioMutingFake(LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam)
{
    TBD_FUNCTION(ParseGetAudioMutingFake);
    BOOL* pfEnabled = NULL;
    HRESULT hr = S_OK;

    pBlob = NULL;
    cbBlob = 0;

    pfEnabled = (BOOL*)AllocBlob(sizeof(BOOL));
    if (!pfEnabled) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    memset(pfEnabled, 0x00, sizeof(BOOL));

    *pfEnabled = FALSE;

    pBlob = (void*)pfEnabled;
    cbBlob = sizeof(BOOL);

Error:
    if (FAILED(hr)) {
        FreeBlob(pfEnabled);
    }
    return hr;
}

//
//
//
HRESULT RILDrv_GetAudioMuting(DWORD dwParam)
{
    TBD_FUNCTION(RILDrv_GetAudioMuting);
    HRESULT hr = S_OK;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    if (IS_ADI && (!IsACallActive() || g_fNoMuteCmdSoFar))
    {
        // The MC75 and TC63 report somtimes the audio is muted, although it is in reality not muted. This happens
        // if no call is active or directly after establishing a call. In these cases we don't ask the modem and directly
        // return "not muted" to the MMI.
        if (!QueueCmd(URC_PORT, pHandle, NULL, CMDOPT_NOOP, APIID_GETAUDIOMUTING, ParseGetAudioMutingFake, NULL, hr)) {
            hr = E_FAIL;
            goto Error;
        }
    }
    else
    {
        if (!QueueCmd(URC_PORT, pHandle, "AT+CMUT?\r", CMDOPT_NONE, APIID_GETAUDIOMUTING, ParseGetAudioMuting, NULL, hr)) {
            hr = E_FAIL;
            goto Error;
        }
    }

Error:
    return hr;
}


//
//
//
HRESULT RILDrv_SetAudioMuting(DWORD dwParam, BOOL fEnable)
{
    TBD_FUNCTION(RILDrv_SetAudioMuting);
    char szCmd[MAX_ATCMD_LEN];
    HRESULT hr = S_OK;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    (void)_snprintfz(szCmd, MAX_ATCMD_LEN, "AT+CMUT=%u\r", (fEnable ? 1 : 0));
    g_dwLastMuteSetting = (fEnable ? 1 : 0);
    if (!QueueCmd(URC_PORT, pHandle, szCmd, CMDOPT_NONE, APIID_SETAUDIOMUTING, NULL, NULL, hr)) {
        hr = E_FAIL;
        goto Error;
    }
    g_fNoMuteCmdSoFar = FALSE;

Error:
    return hr;
}
